﻿#region Copyright and License Notice
// Copyright (C)2010-2016 - Rob Levine and other contributors
// http://configgen.codeplex.com
// 
// This file is part of ConfigGen.
// 
// ConfigGen is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// ConfigGen is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License and 
// the GNU Lesser General Public License along with ConfigGen.  
// If not, see <http://www.gnu.org/licenses/>
#endregion

using System;
using System.IO;
using System.Linq;
using System.Text;
using ConfigGen.Utilities.Xml;
using NUnit.Framework;

namespace ConfigGen.Utilities.Tests.Xml
{
    [TestFixture]
    public class XmlStreamFormatterTests
    {
        [Test]
        public void Format001_SingleChildElement()
        {
            const string xml = @"<root><child /></root>";

            string output = RunCopyTest(xml);

            Assert.AreEqual(xml, output);
        }

        [Test]
        public void Format002_TextContents()
        {
            const string xml = @"<root>SomeText</root>";

            string output = RunCopyTest(xml);

            Assert.AreEqual(xml, output);
        }

        [Test]
        public void Format003_Attributes()
        {
            const string xml = @"<root attribute1=""value1"" attribute2=""value2"" />";

            string output = RunCopyTest(xml);

            Assert.AreEqual(xml, output);
        }

        [Test]
        public void Format004_AttributesAndChildElement()
        {
            const string xml = @"<root attribute1=""value1"" attribute2=""value2""><child /></root>";

            string output = RunCopyTest(xml);

            Assert.AreEqual(xml, output);
        }

        [Test]
        public void Format006a_XmlDeclaration_WithEncoding()
        {
            string xml = @"<?xml version=""1.0"" encoding=""utf-8""?><root><child /></root>";

            string output = RunCopyTest(xml);

            Assert.AreEqual(xml, output);
        }

        [Test]
        public void Format006b_XmlDeclaration_Windows1252Encoding()
        {
            string xml = @"<?xml version=""1.0"" encoding=""Windows-1252""?><root><child /></root>";

            string output = RunCopyTest(xml);

            Assert.AreEqual(xml, output);
        }

        [Test]
        public void Format007_CDATA()
        {
            const string xml = @"<root><![CDATA[SomeText]]></root>";

            string output = RunCopyTest(xml);

            Assert.AreEqual(xml, output);
        }

        [Test]
        public void Format008_Comments()
        {
            const string xml = @"<root><!-- This is my comment --></root>";

            string output = RunCopyTest(xml);

            Assert.AreEqual(xml, output);
        }

        [Test]
        public void Format009_ProcessingInstruction()
        {
            const string xml = @"<root><?processing instruction?></root>";

            string output = RunCopyTest(xml);

            Assert.AreEqual(xml, output);
        }

        [Test]
        public void Format010_Whitespace_Ignored()
        {
            const string xml = @"<root>  </root>";
            const string expected = @"<root />";
            string output = RunCopyTest(xml);

            Assert.AreEqual(expected, output);
        }

        [Test]
        public void Format011_Whitespace()
        {
            const string xml = @"<root>  <child />  </root>";
            const string expected = @"<root><child /></root>";
            string output = RunCopyTest(xml);

            Assert.AreEqual(expected, output);
        }

        [Test]
        public void Format012_ElementWithNamespace()
        {
            const string xml = @"<root xmlns:ns=""http://test/""><ns:child /></root>";

            string output = RunCopyTest(xml);

            Console.WriteLine(output);
            Assert.AreEqual(xml, output);
        }

        [Test]
        public void Format013_AttributeWithNamespace()
        {
            const string xml = @"<root xmlns:ns=""http://test/""><child ns:attr1=""value1"" /></root>";

            string output = RunCopyTest(xml);

            Console.WriteLine(output);
            Assert.AreEqual(xml, output);
        }

        /// <summary>
        /// Regression test: escaped entities should be written out as-is, not "unescaped"
        /// Issue http://configgen.codeplex.com/workitem/9 - "PrettyPrint xml formatter is erroneously unescaping escaped entities."
        /// </summary>
        [Test]
        public void Format014_EntitiesInAttributesAreWrittenOutVerbatim()
        {
            const string xml = @"<root attribute1=""&lt;triangular braces&gt;"" />";

            string output = RunCopyTest(xml);

            Assert.AreEqual(xml, output);
        }

        /// <summary>
        /// Regression test: escaped entities should be written out as-is, not "unescaped"
        /// Issue http://configgen.codeplex.com/workitem/9 - "PrettyPrint xml formatter is erroneously unescaping escaped entities."
        /// </summary>
        [Test]
        public void Format015_EntitiesAsTextContentAreWrittenOutVerbatim()
        {
            const string xml = @"<root>&lt;triangular braces&gt;</root>";

            string output = RunCopyTest(xml);

            Assert.AreEqual(xml, output);
        }

        /// <summary>
        /// Regression test: escaped entities should be written out as-is, not "unescaped"
        /// </summary>
        [Test]
        public void WrapLongElementLines001_SingleAttribute_BelowWrapThreshold()
        {
            const string xml = @"<root attribute=""1"" />"; // length=19 to end of attribute

            var copierOptions = XmlStreamFormatterOptions.Default;
            copierOptions.WrapLongElementLines = true;
            copierOptions.MaxElementLineLength = 19;

            string output = RunCopyTest(xml, copierOptions);

            Console.WriteLine(output);
            Assert.AreEqual(xml, output);
        }

        [Test]
        public void WrapLongElementLines002_SingleAttribute_AboveWrapThreshold()
        {
            const string xml = @"<root attribute=""1"" />"; // length=19 to end of attribute
            const string expected = 
@"<root
   attribute=""1"" />";
   
            var copierOptions = XmlStreamFormatterOptions.Default;
            copierOptions.WrapLongElementLines = true;
            copierOptions.MaxElementLineLength = 18;

            string output = RunCopyTest(xml, copierOptions);

            Console.WriteLine(output);
            Assert.AreEqual(expected, output);
        }

        [Test]
        public void WrapLongElementLines003_MultipleAttributes_AboveWrapThreshold()
        {
            const string xml = @"<root a=""1"" b=""2"" longerattribute=""3"" d=""4"" e=""5""/>";
            const string expected =
@"<root a=""1"" b=""2""
   longerattribute=""3""
   d=""4"" e=""5"" />";

            var copierOptions = XmlStreamFormatterOptions.Default;
            copierOptions.WrapLongElementLines = true;
            copierOptions.MaxElementLineLength = 18;

            string output = RunCopyTest(xml, copierOptions);

            Console.WriteLine(output);
            Assert.AreEqual(expected, output);
        }
        
        [Test]
        public void WrapLongElementLines004_WrapsLineLongLineOnChildElement()
        {
            const string xml = @"<root xmlns:ns=""http://test/""><child attr1=""value1"" attr2=""value2""/></root>";
            const string expected = 
@"<root xmlns:ns=""http://test/"">
   <child attr1=""value1""
      attr2=""value2"" />
</root>";
            var copierOptions = XmlStreamFormatterOptions.Default;
            copierOptions.WrapLongElementLines = true;
            copierOptions.MaxElementLineLength = 30;

            string output = RunCopyTest(xml, copierOptions);

            Console.WriteLine(output);
            Assert.AreEqual(expected, output);
        }

        /// <summary>
        /// Regression test: initially the attribute "data" followed "authentication" without a space
        /// </summary>
        [Test]
        public void WrapLongElementLines005_Regression_SpaceMissingBetweenAttributes()
        {
            const string xml =
@"<MyService endpoint=""http://somebigdomainname.com/SomeEndpoint"" authentication=""integrated"" data=""datavalue"" data2=""datavalue2"" />";
            const string expected = 
@"<MyService endpoint=""http://somebigdomainname.com/SomeEndpoint""
   authentication=""integrated"" data=""datavalue"" data2=""datavalue2"" />";
            var copierOptions = XmlStreamFormatterOptions.Default;
            copierOptions.WrapLongElementLines = true;
            copierOptions.MaxElementLineLength = 70;

            string output = RunCopyTest(xml, copierOptions);

            Console.WriteLine(output);
            Assert.AreEqual(expected, output);
        }

        /// <summary>
        /// Regression test: Element closing braces were mistakenly wrapping to newline
        /// </summary>
        [Test]
        public void WrapLongElementLines006_Regression_ClosingBracesOnNewline()
        {
            const string xml =
@"<configuration xmlns:cg=""http://roblevine.co.uk/Namespaces/ConfigGen/1/0/"">
  <configSections>
    <section name=""MyCustomConfigSection"" type=""MyAssembly.ConfigSections.MyCustomConfigSection, MyAssembly"" />
  </configSections>
  <appSettings>
    <add key=""Environment"" value=""[%Environment%]"" />
  </appSettings>
</configuration>";
            const string expected =
@"<configuration
   xmlns:cg=""http://roblevine.co.uk/Namespaces/ConfigGen/1/0/"">
   <configSections>
      <section
         name=""MyCustomConfigSection""
         type=""MyAssembly.ConfigSections.MyCustomConfigSection, MyAssembly"" />
   </configSections>
   <appSettings>
      <add
         key=""Environment""
         value=""[%Environment%]"" />
   </appSettings>
</configuration>";
            var copierOptions = XmlStreamFormatterOptions.Default;
            copierOptions.WrapLongElementLines = true;
            copierOptions.MaxElementLineLength = 18;

            string output = RunCopyTest(xml, copierOptions);

            Console.WriteLine(output);
            Assert.AreEqual(expected, output);
        }

        /// <summary>
        /// Regression test: The indentation was being corrupted as the reader part of the xml stream formatter was not
        /// ignoring insigificant whitespace, so this was being carried over to the output.
        /// </summary>
        [Test]
        public void WrapLongElementLines007_WrapIndentationIssue()
        {
            const string xml =
@"<configuration>
  <system.web>
    <compilation debug=""true"">
        <assemblies><a>
          <add assembly=""System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"" />
        </a></assemblies>
    </compilation>
  </system.web>
</configuration>";

            // note 3 char indentation of elements and assembly attribute, relative to parents. 
            // This is because our IndentChars is three characters long
            const string expected =
@"<configuration>
   <system.web>
      <compilation debug=""true"">
         <assemblies>
            <a>
               <add
                  assembly=""System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"" />
            </a>
         </assemblies>
      </compilation>
   </system.web>
</configuration>";

            var copierOptions = XmlStreamFormatterOptions.Default;
            copierOptions.WrapLongElementLines = true;
            copierOptions.MaxElementLineLength = 100;

            string output = RunCopyTest(xml, copierOptions);

            Console.WriteLine(output);
            Assert.AreEqual(expected, output);
        }

        public string RunCopyTest(string input)
        {
            var copierOptions = XmlStreamFormatterOptions.Default;
            copierOptions.Indent = false;
            return RunCopyTest(input, copierOptions);
        }

        public string RunCopyTest(string input, XmlStreamFormatterOptions formatterOptions)
        {
            var encoding = Encoding.UTF8;
            using (var readerStream = new MemoryStream(encoding.GetBytes(input).ToArray()))
            {
                using (var writerStream = new MemoryStream())
                {
                    var copier = new XmlStreamFormatter();
                    copier.Initialise(readerStream, writerStream);
                    copier.FormatterOptions = formatterOptions;
                    copier.Format();
                    writerStream.Position = 0;
                    var byteArray = writerStream.ToArray();

                    var ret = encoding.GetString(byteArray);

                    // TODO: RJL: this isn't right - this shouldn't be necessary. Is this a side effect of doing this via the encoding class?
                    if (ret[0] != '<')
                    {
                        // remove BOM if present. RJL - could be a little more scientific here!
                        ret = ret.Substring(1);
                    }

                    return ret;
                }
            }
        }
    }
}
